#ifndef __ADC_H
#define __ADC_H

#include "./dma.h"
#include <stddef.h>

namespace stm32f1::adc {

#define ADC_INSTANCE(BASE) reinterpret_cast<ADC_TypeDef *>(BASE)

template <uint32_t BASE, uint32_t FIELD>
struct Adc : public rcc::APB2Bus<FIELD> {

    static constexpr uint32_t BASE_VAL = BASE;
    static constexpr uint32_t DR_ADDR_VAL = BASE + offsetof(ADC_TypeDef, DR);

    static inline void convert_init() {
        ADC_INSTANCE(BASE_VAL)->CR2 = 0x00;
        ADC_INSTANCE(BASE_VAL)->CR1 = ADC_CR1_SCAN;
        ADC_INSTANCE(BASE_VAL)->CR2 =
            ADC_CR2_CONT | (0x07 << 17) | ADC_CR2_DMA | ADC_CR2_EXTTRIG;
        ADC_INSTANCE(BASE_VAL)->SMPR1 = 0xFFFFFFFF;
        ADC_INSTANCE(BASE_VAL)->SMPR2 = 0xFFFFFFFF;
    }

    static inline void adjust() {
        ADC_INSTANCE(BASE_VAL)->CR2 |= ADC_CR2_ADON;

        ADC_INSTANCE(BASE_VAL)->CR2 |= ADC_CR2_RSTCAL;
        while ((ADC_INSTANCE(BASE_VAL)->CR2 & ADC_CR2_RSTCAL) != 0)
            ;

        ADC_INSTANCE(BASE_VAL)->CR2 |= ADC_CR2_CAL;
        while ((ADC_INSTANCE(BASE_VAL)->CR2 & ADC_CR2_CAL) != 0)
            ;
    }

    static inline void start() {
        ADC_INSTANCE(BASE_VAL)->CR2 |= ADC_CR2_SWSTART;
    }
};

namespace inner {

template <uint32_t BEGIN, uint32_t END, uint32_t N>
constexpr inline uint32_t sum_sqr_mask() {
    if constexpr ((N >= BEGIN) && (N <= END)) {
        constexpr uint32_t MASK = 0x1F << ((N - BEGIN) * 5);
        return MASK | sum_sqr_mask<BEGIN, END, N + 1>();
    } else {
        return 0;
    }
}

template <uint32_t BEGIN, uint32_t END, uint32_t N, uint8_t CHAN,
          uint8_t... Chans>
constexpr inline uint32_t sum_sqr_val() {
    if constexpr ((N >= BEGIN) && (N <= END)) {
        constexpr uint32_t MASK = CHAN << ((N - BEGIN) * 5);
        if constexpr (sizeof...(Chans) != 0) {
            return MASK | sum_sqr_val<BEGIN, END, N + 1, Chans...>();
        } else {
            return MASK;
        }
    } else {
        if constexpr (sizeof...(Chans) != 0) {
            return sum_sqr_val<BEGIN, END, N + 1, Chans...>();
        } else {
            return 0;
        }
    }
}

template <typename ADC, uint8_t... Chans> inline void config_chan() {
    constexpr uint32_t BASE = ADC::BASE_VAL;
    constexpr uint32_t COUNT = sizeof...(Chans);

    static_assert((COUNT > 0) && (COUNT <= 16), "COUNT INVALID");

    constexpr uint32_t COUNT_MASK = 0x0F << 20;
    ADC_INSTANCE(BASE)->SQR1 =
        ADC_INSTANCE(BASE)->SQR1 & (~COUNT_MASK) | ((COUNT - 1) << 20);

    if constexpr (COUNT <= 6) {
        constexpr uint32_t SQR3_SUM_MASK = sum_sqr_mask<1, COUNT, 1>();
        constexpr uint32_t SQR3_SUM_VAL = sum_sqr_val<1, COUNT, 1, Chans...>();
        ADC_INSTANCE(BASE)->SQR3 =
            ADC_INSTANCE(BASE)->SQR3 & (~SQR3_SUM_MASK) | SQR3_SUM_VAL;
        return;

    } else {
        constexpr uint32_t SQR3_SUM_MASK = sum_sqr_mask<1, 6, 1>();
        constexpr uint32_t SQR3_SUM_VAL = sum_sqr_val<1, 6, 1, Chans...>();
        ADC_INSTANCE(BASE)->SQR3 =
            ADC_INSTANCE(BASE)->SQR3 & (~SQR3_SUM_MASK) | SQR3_SUM_VAL;

        if constexpr (COUNT <= 12) {
            constexpr uint32_t SQR2_SUM_MASK = sum_sqr_mask<7, COUNT, 7>();
            constexpr uint32_t SQR2_SUM_VAL =
                sum_sqr_val<7, COUNT, 7, Chans...>();
            ADC_INSTANCE(BASE)->SQR2 =
                ADC_INSTANCE(BASE)->SQR2 & (~SQR2_SUM_MASK) | SQR2_SUM_VAL;
            return;
        }

        constexpr uint32_t SQR2_SUM_MASK = sum_sqr_mask<7, 12, 7>();
        constexpr uint32_t SQR2_SUM_VAL = sum_sqr_val<7, 12, 7, Chans...>();
        ADC_INSTANCE(BASE)->SQR2 =
            ADC_INSTANCE(BASE)->SQR2 & (~SQR2_SUM_MASK) | SQR2_SUM_VAL;

        if constexpr (COUNT <= 16) {
            constexpr uint32_t SQR1_SUM_MASK = sum_sqr_mask<13, COUNT, 13>();
            constexpr uint32_t SQR1_SUM_VAL =
                sum_sqr_val<13, COUNT, 13, Chans...>();
            ADC_INSTANCE(BASE)->SQR1 =
                ADC_INSTANCE(BASE)->SQR1 & (~SQR1_SUM_MASK) | SQR1_SUM_VAL;
            return;
        }
    }
}

} // namespace inner

template <typename ADC, uint8_t... Chans>
inline void config(volatile uint16_t *data_buf) {
    ADC::convert_init();
    inner::config_chan<ADC, Chans...>();
    ADC::adjust();

    using DMA_CHAN = typename dma::DmaSel<ADC::BASE_VAL>::CHAN;
    constexpr uint16_t COUNT = sizeof...(Chans);

    DMA_CHAN::template set_cfg<dma::AdcCfg, ADC::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(data_buf), COUNT);
    ADC::start();
}

using Adc1 = Adc<ADC1_BASE, RCC_APB2ENR_ADC1EN>;
using Adc2 = Adc<ADC2_BASE, RCC_APB2ENR_ADC2EN>;
using Adc3 = Adc<ADC3_BASE, RCC_APB2ENR_ADC3EN>;

} // namespace stm32f1::adc

#endif
